package lru

import (
	"container/list"
	"time"
)

// Hash table + bidirectional linked list to achieve lru
type Cache struct {
	capacity int                      // Maximum usable capacity
	size     int                      // Used capacity
	list     *list.List               // doubly linked
	cache    map[string]*list.Element // Hash table

}

// Type of node
type entry struct {
	key        string
	value      string
	expireTime int64 // Expiration time
}

func NewCache(capacity int) *Cache {
	return &Cache{
		capacity: capacity,
		list:     list.New(),
		cache:    make(map[string]*list.Element),
	}
}

func (c *Cache) Get(key string) (string, bool) {
	if element, ok := c.cache[key]; ok {
		expireTime := element.Value.(*entry).expireTime
		if expireTime == -1 || time.Now().Unix() <= expireTime {
			c.list.MoveToFront(element)
			ety := element.Value.(*entry)
			return ety.value, true
		} else {
			c.Delete(element.Value.(*entry).key)
		}
	}
	return "", false
}

func (c *Cache) Set(key string, value string, ttl ...int64) {
	var elemTTl int64
	if len(ttl) == 1 {
		elemTTl = time.Now().Unix() + ttl[0]
	} else {
		elemTTl = -1
	}
	if element, ok := c.cache[key]; ok {
		c.list.MoveToFront(element)
		ety := element.Value.(*entry)
		ety.value = value
		ety.expireTime = elemTTl
	} else {
		element := c.list.PushFront(&entry{key: key, value: value, expireTime: elemTTl})
		c.cache[key] = element
		c.size++
	}

	if c.size > c.capacity {
		c.checkExpired()
	}
	if c.size > c.capacity {
		c.removeOldEntry()
	}
}

func (c *Cache) Delete(key string) bool {
	if element, ok := c.cache[key]; ok {
		c.list.Remove(element)
		delete(c.cache, key)
		c.size--
		return true
	}
	return false
}

// TODO: When the linked list is large, the loop traversal node is time-consuming to be optimized
func (c *Cache) checkExpired() {
	now := time.Now().Unix()
	element := c.list.Front()
	for element != nil {
		expireTime := element.Value.(*entry).expireTime
		if expireTime != -1 && now > expireTime {
			c.Delete(element.Value.(*entry).key)
		}
		element = element.Next()
	}
}

// Deletes a least recently used piece of data
func (c *Cache) removeOldEntry() {
	element := c.list.Back()
	c.list.Remove(element)
	ety := element.Value.(*entry)
	delete(c.cache, ety.key)
	c.size--
}

func (c *Cache) UpdateCapacity(capacity int) {
	c.capacity = capacity
}

func (c *Cache) CacheData() map[string]*list.Element {
	return c.cache
}
